#pragma once

#include "PLNonLinearLeastSquaresSolver.h"
#include "PLBlob.h"
#include "PLEnums.h"

#include <map>
#include <set>
#include <string>
#include <vector>

/**
  *	A PLAdjacentVertexPair capsules two const pointers to PLVertex objects
  */
struct PLAdjacentVertexPair
{
	/**
	  *	Constructs a new PLAdjacentVertexPair object with the given pointers
	  *
	  * @param first const pointer to the first PLVertex object
	  * @param second const pointer to the second PLVertex object
	  */
	PLAdjacentVertexPair( const PLVertex *first, const PLVertex *second ) :
		mFirst(first),
		mSecond(second) {};

	/** First vertex */
	const PLVertex *mFirst;
	/** Second vertex */
	const PLVertex *mSecond;
};

/**
  *	A PLAdjacentVertexPairCompare object is used as a Compare-object in an
  * stl container.
  */
class PLAdjacentVertexPairCompare
{
public:
	bool operator()(const PLAdjacentVertexPair &first, const PLAdjacentVertexPair &second) const;
};

/**
  *	A PLAdjacentVertexPairPCompare object is used as a Compare-object in an
  * stl container.
  */
class PLAdjacentVertexPairPCompare
{
public:
	bool operator()(const PLAdjacentVertexPair *first, const PLAdjacentVertexPair *second) const;
};

/**
  *	A PLPlanarPolygon3dsOptimizationOptions object capsules the options needed
  * for a PLPlanarPolygon3dsOptimization object.
  */
class PLPlanarPolygon3dsOptimizationOptions : public PLNonLinearLeastSquaresSolverOptions
{
public:
	PLPlanarPolygon3dsOptimizationOptions(
		double minResidual = 0.0, PLuint maxIterations = 1000,
		PLuint maxLineSearchSteps = 1000, double minGradientAbsComponent = 1.0e-6,
		double secondTermWeight = 100, double thirdTermWeight = 0.001 ) : PLNonLinearLeastSquaresSolverOptions( minResidual, maxIterations, maxLineSearchSteps, minGradientAbsComponent )
	{
		mSecondTermWeight = secondTermWeight;
		mThirdTermWeight = thirdTermWeight;
		mChannel = PL_CHANNEL_RED;
	}

	/** Weight of the second term corresponding to the difference between color values of adjacent PLVertex objects */
	double mSecondTermWeight;
	/** Weight of the second term corresponding to the difference between the original color and the leveled color of a PLVertex object */
	double mThirdTermWeight;
	/** Enum value, that specifies the current channel of interest (Red, Green or Blue) */
	PLChannel mChannel;
};

/**
  *	A PLPlanarPolygon3dsOptimizationOptions object is a solver
  * of a non linear equation system that occurs when a leveling function
  * for the textures of a model has to be calculated.
  */
class PLPlanarPolygon3dsOptimization : public PLNonLinearLeastSquaresSolver
{
public:
	PLPlanarPolygon3dsOptimization(
		std::map<std::string, std::set<PLBlob*>> *materialNamesToBlobsMap,
		const PLPlanarPolygon3dsOptimizationOptions &options);
	~PLPlanarPolygon3dsOptimization( void );
	virtual std::string GetCurrentChannelName( void ) const;
	virtual double Residual( void ) const;
	virtual double FirstTermResidual( void ) const;
	virtual double SecondTermResidual( void ) const;
	virtual double ThirdTermResidual( void ) const;
	void SetCurrentChannel( PLChannel channel );
		
protected:
	virtual void BackupVariables( std::vector<double> *backup );
	virtual void BuildLinearEquationSystem( void );
	virtual void FinalizeSolver( void );
	virtual void Initialize( void );
	virtual void RestoreVariables( const std::vector<double> &backup );
	virtual void UpdateVariables( const std::vector<double> &gradient );

private:
	/** Necessary options for the PLPlanarPolygon3dsOptimization object */
	PLPlanarPolygon3dsOptimizationOptions mOptions;
	/** Map, that maps image names (not paths!) to sets with all the pointers to PLBlob objects that correspond to one particular image */
	std::map<std::string, std::set<PLBlob*>> *mMaterialNamesToBlobsMap;
	/** Map, that maps an address of a PLVertex object to the index in the mVertexes vector */
	std::map<const PLVertex*, PLuint> mVertexAdressToIndexMap;
	/** Set of all adjacent vertex pairs in the model */
	std::set<PLAdjacentVertexPair, PLAdjacentVertexPairCompare> mAdjacentVertexPairs;

	/** Mean grey values of the single channels before the leveling */
	double mOriginalMeanGreyValues[3];
	/** Color ranges of the single channels before the leveling */
	double mOriginalColorRanges[3];

	/** Vector of pointers to all the PLVertex objects, that play a role in the non linear equation system */
	std::vector<PLVertex*> mVertexes;
	/** Vector of pointers to all the PLEdge objects, that play a role in the non linear equation system */
	std::vector<PLEdge*> mEdges;

	void VertexToOriginalVertexTerm( void );
	double VertexToOriginalVertexResidual( void ) const;
};